﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Legend.Collections;
using NUnit.Framework;
using FakeItEasy; using FakeItEasy.ExtensionSyntax.Full;
using System.Collections;
using System.Threading;
using Legend.Threading;
using System.Linq.Expressions;

namespace Legend.Core.Tests.Collections.SynchronizedCollectionTests
{
    public class SynchronizedCollectionTestCase
    {
        protected SynchronizedCollection<string> collection;
        protected ICollection<string> wrappedCollection;
        protected ISynchronizationManager readerWriterLock;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            readerWriterLock = A.Fake<ISynchronizationManager>();
            A.CallTo(() => readerWriterLock.RecursionPolicy).Returns(System.Threading.LockRecursionPolicy.SupportsRecursion);

            wrappedCollection = CreateWrappedCollection();
            collection = new SynchronizedCollection<string>(wrappedCollection, readerWriterLock);
        }

        protected virtual ICollection<string> CreateWrappedCollection()
        {
            return A.Fake<ICollection<string>>();
        }
    }

    [TestFixture]
    public class Constructor
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_collection_is_null()
        {
            var c = new SynchronizedCollection<string>(null, A.Fake<ISynchronizationManager>());
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_readerWriterLock_is_null()
        {
            var c = new SynchronizedCollection<string>(new List<string>(), null);
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void should_throw_exception_when_readerWriterLock_does_not_allow_recursion()
        {
            var readerWriterLock = A.Fake<ISynchronizationManager>();
            A.CallTo(() => readerWriterLock.RecursionPolicy).Returns(System.Threading.LockRecursionPolicy.NoRecursion);

            var c = new SynchronizedCollection<string>(
                new List<string>(),
                readerWriterLock);
        }
    }

    [TestFixture]
    public class Add
        : SynchronizedCollectionTestCase 
    {
        [Test]
        public void should_delegate_call_to_wrapped_collection()
        {
            this.collection.Add("foo");
            
            A.CallTo(() => this.wrappedCollection.Add("foo")).MustHaveHappened();
        }

        [Test]
        public void should_acquire_writer_lock()
        {
            this.collection.Add("foo");
            
            A.CallTo(() => this.readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_realease_lock()
        {
            this.collection.Add("foo");
            
            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_wrapped_collection_throws_exception_in_add()
        {
            A.CallTo(() => wrappedCollection.Add(null))
                .Throws(new ArgumentNullException());

            try
            {
                this.collection.Add(null);
            }
            catch (ArgumentNullException) { }

            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }
    }

    public abstract class ReadMethodTestCase
        : SynchronizedCollectionTestCase 
    {
        protected abstract void CollectionCall();
        protected abstract Expression<Action> WrappedCollectionCall();

        [Test]
        public void should_delegate_call_to_wrapped_collection()
        {
            CollectionCall();
            
            A.CallTo(WrappedCollectionCall()).MustHaveHappened();
        }

        [Test]
        public void should_acquire_read_lock()
        {
            CollectionCall();
            
            A.CallTo(() => readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock()
        {
            CollectionCall();
            
            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock_when_wrapped_collection_throws_exception()
        {

            A.CallTo(WrappedCollectionCall()).Throws(new NotSupportedException());

            try
            {
                CollectionCall();
            }
            catch (NotSupportedException) { }

            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class Clear
        : SynchronizedCollectionTestCase 
    {
        [Test]
        public void should_delegate_call_to_wrapped_collection()
        {
            this.collection.Clear();

            A.CallTo(() => wrappedCollection.Clear()).MustHaveHappened();
        }

        [Test]
        public void should_acquire_write_lock()
        {
            collection.Clear();
           
            A.CallTo(() => readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_write_lock()
        {
            collection.Clear();
            
            A.CallTo(() => readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_write_lock_when_exception_is_thrown_in_wrappedCollection()
        {
            A.CallTo(() => wrappedCollection.Clear())
                .Throws(new NotSupportedException());

            try
            {
                collection.Clear();
            }
            catch (NotSupportedException) { }

            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();

        }
    }

    [TestFixture]
    public class Contains
        : ReadMethodTestCase
    {
        [Test]
        public void should_return_false_when_wrapped_collection_returns_false()
        {
            wrappedCollection
                .CallsTo(x => x.Contains("a"))
                .Returns(false);

            var returned = collection.Contains("a");
            Assert.IsFalse(returned);
        }

        [Test]
        public void should_return_true_when_wrapped_collection_returns_true()
        {
            wrappedCollection
                .CallsTo(x => x.Contains("a"))
                .Returns(true);

            var returned = collection.Contains("a");
            Assert.IsTrue(returned);
        }

        protected override void CollectionCall()
        {
            collection.Contains("a");
        }

        protected override Expression<Action> WrappedCollectionCall()
        {
            return () => this.wrappedCollection.Contains("a");
        }
    }

    [TestFixture]
    public class CopyTo
        : ReadMethodTestCase
    {
        string[] array = new string[10];

        protected override void CollectionCall()
        {
            collection.CopyTo(array, 0);
        }

        protected override Expression<Action> WrappedCollectionCall()
        {
            return () => this.wrappedCollection.CopyTo(array, 0);
        }
    }

    //[TestFixture]
    //public class Count
    //    : ReadMethodTestCase
    //{

    //    protected override void CollectionCall()
    //    {
    //        var i = collection.Count;
    //    }

    //    protected override Expression<Action> WrappedCollectionCall(ICollection<string> wrappedCollection)
    //    {
    //        int i = 0;
    //        return () => wrappedCollection.Count;
    //    }
    //}

    [TestFixture]
    public class IsReadOnly
        : SynchronizedCollectionTestCase 
    {
        [Test]
        public void should_return_true_when_wrapped_collection_returns_true()
        {
            wrappedCollection.CallsTo(x => x.IsReadOnly).Returns(true);
            Assert.IsTrue(collection.IsReadOnly);
        }

        [Test]
        public void should_return_false_when_wrapped_collection_returns_false()
        {
            wrappedCollection.CallsTo(x => x.IsReadOnly).Returns(false);
            Assert.IsFalse(collection.IsReadOnly);
        }
    }

    [TestFixture]
    public class Remove
        : SynchronizedCollectionTestCase
    {
        [Test]
        public void should_call_remove_on_wrapped_collection()
        {
            collection.Remove("foo");
            A.CallTo(() => wrappedCollection.Remove("foo")).MustHaveHappened();
        }

        [Test]
        public void should_return_true_when_wrapped_collection_returns_true()
        {
            wrappedCollection.CallsTo(x => x.Remove("foo")).Returns(true);
            Assert.IsTrue(collection.Remove("foo"));
        }

        [Test]
        public void should_return_false_when_wrapped_collection_returns_false()
        {
            wrappedCollection.CallsTo(x => x.Remove("foo")).Returns(false);
            Assert.IsFalse(collection.Remove("foo"));
        }

        [Test]
        public void should_acquire_writer_lock()
        {
            this.collection.Remove("foo");
            A.CallTo(() => this.readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_realease_lock()
        {
            this.collection.Remove("foo");
            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_wrapped_collection_throws_exception()
        {
            this.wrappedCollection
                .CallsTo(x => x.Remove(null))
                .Throws(new ArgumentNullException());

            try
            {
                this.collection.Remove(null);
            }
            catch (ArgumentNullException) { }

            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class GetEnumerator
        : SynchronizedCollectionTestCase 
    {
        IEnumerator<string> enumerator;

        protected override void OnSetUp()
        {
            base.OnSetUp();
            enumerator = A.Fake<IEnumerator<string>>();
            wrappedCollection.CallsTo(x => x.GetEnumerator()).Returns(enumerator);
        }

        [Test]
        public void should_call_get_enumerator_on_wrapped_collection()
        {
            collection.GetEnumerator();
            A.CallTo(() => wrappedCollection.GetEnumerator()).MustHaveHappened();
        }

        [Test]
        public void should_return_a_locking_enumerator()
        {
            var e = collection.GetEnumerator();
            Assert.AreEqual(typeof(LockingEnumerator<string>), e.GetType());
        }
    }

    [TestFixture]
    public class non_generic_GetEnumerator
        : SynchronizedCollectionTestCase
    {
        IEnumerator<string> enumerator;

        protected override void OnSetUp()
        {
            base.OnSetUp();
            enumerator = A.Fake<IEnumerator<string>>();
            wrappedCollection.CallsTo(x => x.GetEnumerator()).Returns(enumerator);
        }

        [Test]
        public void should_call_get_enumerator_on_wrapped_collection()
        {
            ((IEnumerable)collection).GetEnumerator();
            A.CallTo(() => wrappedCollection.GetEnumerator()).MustHaveHappened();
        }

        [Test]
        public void should_return_a_locking_enumerator()
        {
            var e = ((IEnumerable)collection).GetEnumerator();
            Assert.AreEqual(typeof(LockingEnumerator<string>), e.GetType());
        }
    }

    [TestFixture]
    public class AcquireReadLock
        : SynchronizedCollectionTestCase
    {
        [Test]
        public void should_acquire_read_lock()
        {
            using (collection.AcquireReadLock()) { }
            A.CallTo(() => readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_disposed()
        {
            using (collection.AcquireReadLock()) { }
            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class AcquireWriteLock
        : SynchronizedCollectionTestCase
    {
        [Test]
        public void should_acquire_read_lock()
        {
            using (collection.AcquireWriteLock()) { }
            A.CallTo(() => readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_disposed()
        {
            using (collection.AcquireWriteLock()) { }
            A.CallTo(() => readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }
    }
}
